/**
 *    Copyright 2009-2019 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package org.apache.ibatis.builder.xml;

import org.apache.ibatis.builder.*;
import org.apache.ibatis.cache.Cache;
import org.apache.ibatis.executor.ErrorContext;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.mapping.*;
import org.apache.ibatis.parsing.XNode;
import org.apache.ibatis.parsing.XPathParser;
import org.apache.ibatis.reflection.MetaClass;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.TypeHandler;

import java.io.InputStream;
import java.io.Reader;
import java.util.*;

/**
 * 处理MapperXml文件的构建工具
 *
 * @author Clinton Begin
 * @author Kazuki Shimizu
 */
public class XMLMapperBuilder extends BaseBuilder {

    /**
     * XML路径解析器
     */
    private final XPathParser parser;
    /**
     * Mapper文件解析助手
     */
    private final MapperBuilderAssistant builderAssistant;
    /**
     * 代码块
     */
    private final Map<String, XNode> sqlFragments;
    /**
     * 对应的Mapper文件
     */
    private final String resource;

    @Deprecated
    public XMLMapperBuilder(Reader reader, Configuration configuration, String resource, Map<String, XNode> sqlFragments, String namespace) {
        this(reader, configuration, resource, sqlFragments);
        this.builderAssistant.setCurrentNamespace(namespace);
    }

    @Deprecated
    public XMLMapperBuilder(Reader reader, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
        this(new XPathParser(reader, true, configuration.getVariables(), new XMLMapperEntityResolver()),
                configuration, resource, sqlFragments);
    }

    public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource, Map<String, XNode> sqlFragments, String namespace) {
        this(inputStream, configuration, resource, sqlFragments);
        this.builderAssistant.setCurrentNamespace(namespace);
    }

    public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
        this(new XPathParser(inputStream, true, configuration.getVariables(), new XMLMapperEntityResolver())/*构建一个XPath解析器*/,
                configuration,/*Mybatis配置*/
                resource/*资源路径*/,
                sqlFragments/*现存的Sql代码块*/
        );
    }

    private XMLMapperBuilder(XPathParser parser, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
        super(configuration);
        this.builderAssistant = new MapperBuilderAssistant(configuration, resource);/*创建Mapper文件解析助手*/
        this.parser = parser;
        this.sqlFragments = sqlFragments;
        this.resource = resource;
    }

    /**
     * MapperXml文件的解析入口方法
     */
    public void parse() {
        if (!configuration.isResourceLoaded(resource)) {
            // 配置文件为第一次加载时才会执行

            // 读取并配置MapperXml文件的内容 核心逻辑
            configurationElement(parser.evalNode("/mapper"));

            // 记录已加载当前的配置文件
            configuration.addLoadedResource(resource);
            // 绑定DAO操作接口和当前配置的关系
            bindMapperForNamespace();
        }

        // 解析未完成处理的ResultMap
        parsePendingResultMaps();
        // 解析未完成处理的缓存引用
        parsePendingCacheRefs();
        // 解析未完成处理的语句
        parsePendingStatements();
    }

    public XNode getSqlFragment(String refid) {
        return sqlFragments.get(refid);
    }

    /**
     * 解析配置mapper节点
     *
     * @param context mapper节点
     */
    private void configurationElement(XNode context) {
        try {
            // 获取当前配置文件的命名空间(工作空间)，通常这个值我们会设置为DAO操作类的全限定名称
            String namespace = context.getStringAttribute("namespace");
            if (namespace == null || namespace.equals("")) {
                throw new BuilderException("Mapper's namespace cannot be empty");
            }
            // 配置当前的命名空间(工作空间)
            builderAssistant.setCurrentNamespace(namespace);

            // 解析缓存引用
            cacheRefElement(context.evalNode("cache-ref"));
            // 解析缓存配置,并给当前命名空间配置一个缓存，默认情况下Mybatis使用PerpetualCache
            cacheElement(context.evalNode("cache"));
            // 解析并注册parameterMap元素
            parameterMapElement(context.evalNodes("/mapper/parameterMap"));

            // 解析并注册resultMap元素
            resultMapElements(context.evalNodes("/mapper/resultMap"));


            // 解析并注册Sql元素,此处只是简单的将所有的SQL片段读取出来，然后放到{@link Configuration#sqlFragments}中,
            // 不会执行太多额外的操作
            sqlElement(context.evalNodes("/mapper/sql"));

            // 构建声明语句(CRUD)
            buildStatementFromContext(context.evalNodes("select|insert|update|delete"));

        } catch (Exception e) {
            throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
        }
    }

    /**
     * 处理所有的【select|insert|update|delete】节点构建声明语句
     *
     * @param list 所有的声明语句节点
     */
    private void buildStatementFromContext(List<XNode> list) {
        if (configuration.getDatabaseId() != null) {
            // 解析专属于当前数据库类型的Statement
            buildStatementFromContext(list, configuration.getDatabaseId());
        }
        // 解析未指定数据库类型的Statement
        buildStatementFromContext(list, null);
    }

    /**
     * 构建声明语句
     *
     * @param list               所有的声明语句
     * @param requiredDatabaseId 必须的数据库类型唯一标志
     */
    private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
        for (XNode context : list) {
            // 解析每一个声明
            // 配置Xml声明解析器
            final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
            try {
                // 委托给XMLStatementBuilder完成Statement的解析
                statementParser.parseStatementNode();
            } catch (IncompleteElementException e) {
                // 发生异常，添加到未完成解析的声明语句集合
                configuration.addIncompleteStatement(statementParser);
            }
        }
    }

    private void parsePendingResultMaps() {
        // 获取所有尚未完成处理的ResultMap集合
        Collection<ResultMapResolver> incompleteResultMaps = configuration.getIncompleteResultMaps();
        synchronized (incompleteResultMaps) {
            Iterator<ResultMapResolver> iter = incompleteResultMaps.iterator();
            while (iter.hasNext()) {
                try {
                    //
                    iter.next().resolve();
                    iter.remove();
                } catch (IncompleteElementException e) {
                    // ResultMap is still missing a resource...
                }
            }
        }
    }

    private void parsePendingCacheRefs() {
        Collection<CacheRefResolver> incompleteCacheRefs = configuration.getIncompleteCacheRefs();
        synchronized (incompleteCacheRefs) {
            Iterator<CacheRefResolver> iter = incompleteCacheRefs.iterator();
            while (iter.hasNext()) {
                try {
                    iter.next().resolveCacheRef();
                    iter.remove();
                } catch (IncompleteElementException e) {
                    // Cache ref is still missing a resource...
                }
            }
        }
    }

    private void parsePendingStatements() {
        Collection<XMLStatementBuilder> incompleteStatements = configuration.getIncompleteStatements();
        synchronized (incompleteStatements) {
            Iterator<XMLStatementBuilder> iter = incompleteStatements.iterator();
            while (iter.hasNext()) {
                try {
                    iter.next().parseStatementNode();
                    iter.remove();
                } catch (IncompleteElementException e) {
                    // Statement is still missing a resource...
                }
            }
        }
    }

    /**
     * 解析cache-ref 节点
     *
     * @param context cache-ref节点
     */
    private void cacheRefElement(XNode context) {
        if (context != null) {
            // 处理引用其他命名空间的缓存
            // 添加缓存引用
            configuration.addCacheRef(builderAssistant.getCurrentNamespace(), context.getStringAttribute("namespace"));
            // 构建缓存引用对象
            CacheRefResolver cacheRefResolver = new CacheRefResolver(builderAssistant, context.getStringAttribute("namespace"));
            try {
                // 解析引用的缓存配置，为builderAssistant设置使用的缓存实例
                cacheRefResolver.resolveCacheRef();
            } catch (IncompleteElementException e) {
                // 如果引用的缓存不存在(可能是还未加载),那么将会添加一个未完成的引用标记，之后会调用该方法完成补偿
                // 添加一个尚未完成的缓存引用标记
                configuration.addIncompleteCacheRef(cacheRefResolver);
            }
        }
    }

    /**
     * 解析cache节点
     * 缓存配置
     */
    private void cacheElement(XNode context) {
        if (context != null) {
            // 二级缓存，默认为PERPETUAL
            String type = context.getStringAttribute("type", "PERPETUAL");
            // 解析出缓存实现类
            Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
            // 缓存回收策略
            //  LRU –  最近最少使用的：移除最长时间不被使用的对象。
            //
            //  FIFO –  先进先出：按对象进入缓存的顺序来移除它们。
            //
            //  SOFT –  软引用：移除基于垃圾回收器状态和软引用规则的对象。
            //
            //  WEAK –  弱引用：更积极地移除基于垃圾收集器状态和弱引用规则的对象。
            // 解析出缓存策略
            String eviction = context.getStringAttribute("eviction", "LRU");
            // 解析出缓存策略类
            Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
            // 设置刷新间隔
            Long flushInterval = context.getLongAttribute("flushInterval");
            // 可用内存大小
            Integer size = context.getIntAttribute("size");
            // 是否为只读缓存，主要读取参数前面的'!'。
            boolean readWrite = !context.getBooleanAttribute("readOnly", false);
            // 阻塞缓存
            boolean blocking = context.getBooleanAttribute("blocking", false);

            // 配置用户自定义参数
            Properties props = context.getChildrenAsProperties();

            // 使用一个新的缓存
            builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
        }
    }

    /**
     * 解析parameterMap节点集合
     *
     * @param list parameterMap节点集合
     */
    private void parameterMapElement(List<XNode> list) {
        // 遍历处理每一个parameterMap节点
        for (XNode parameterMapNode : list) {
            // 获取parameterMap的唯一标志
            String id = parameterMapNode.getStringAttribute("id");
            // 获取parameterMap的类型的名称
            String type = parameterMapNode.getStringAttribute("type");
            // 解析出parameterMap的类型
            Class<?> parameterClass = resolveClass(type);
            // 获取所有parameter子节点
            List<XNode> parameterNodes = parameterMapNode.evalNodes("parameter");
            List<ParameterMapping> parameterMappings = new ArrayList<>();
            for (XNode parameterNode : parameterNodes) {
                // 参数名称
                String property = parameterNode.getStringAttribute("property");
                // 参数Java类型
                String javaType = parameterNode.getStringAttribute("javaType");
                // 参数jdbc类型
                String jdbcType = parameterNode.getStringAttribute("jdbcType");
                // 返回类型
                String resultMap = parameterNode.getStringAttribute("resultMap");
                // 该参数主要用于存储过程，分别：IN 表示入参，OUT表示出参，INOUT表示出入参
                String mode = parameterNode.getStringAttribute("mode");
                // 指定类型处理器
                String typeHandler = parameterNode.getStringAttribute("typeHandler");
                // 确定小数点后保留的位数
                Integer numericScale = parameterNode.getIntAttribute("numericScale");
                // 解析参数类型
                ParameterMode modeEnum = resolveParameterMode(mode);
                // 解析java类型
                Class<?> javaTypeClass = resolveClass(javaType);
                // 解析jdbc类型
                JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
                // 解析出类型处理器
                Class<? extends TypeHandler<?>> typeHandlerClass = resolveClass(typeHandler);
                ParameterMapping parameterMapping = builderAssistant.buildParameterMapping(parameterClass, property, javaTypeClass, jdbcTypeEnum, resultMap, modeEnum, typeHandlerClass, numericScale);
                // 添加到已解析出的参数映射表
                parameterMappings.add(parameterMapping);
            }
            // 注册参数映射表
            builderAssistant.addParameterMap(id, parameterClass, parameterMappings);
        }
    }

    /**
     * 解析 /mapper/resultMap节点结合
     *
     * @param list /mapper/resultMap节点结合
     */
    private void resultMapElements(List<XNode> list) throws Exception {
        for (XNode resultMapNode : list) {
            try {
                // 解析ResultMap节点
                resultMapElement(resultMapNode);
            } catch (IncompleteElementException e) {
                // ignore, it will be retried
                // 在内部实现中，未完成解析的节点将会被放至Configuration#incompleteResultMaps中
            }
        }
    }

    /**
     * 解析ResultMap节点
     *
     * @param resultMapNode ResultMap节点
     */
    private ResultMap resultMapElement(XNode resultMapNode) throws Exception {
        return resultMapElement(resultMapNode, Collections.<ResultMapping>emptyList(), null);
    }

    /**
     * 解析ResultMap元素，关于ResultMap的各个子元素的作用可以参考文档{@link https://blog.csdn.net/u012702547/article/details/54599132}
     * <p>
     * 该方法并不是单纯的只用于解析ResultMap元素，而是用于解析具有ResultMap性质的元素，该方法的调用方，目前有两个，一个是用来解析`ResultMap`元素，
     * 另一个使用该方法来解析association/collection/discriminator的case元素。
     *
     * @param resultMapNode            resultMapNode节点
     * @param additionalResultMappings 现有的resultMapping结合
     * @param enclosingType            返回类型
     */
    private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings, Class<?> enclosingType) throws Exception {
        ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
        // 获取唯一标志,有趣的是association/collection/discriminator的case元素都没有ID属性，所以该ID会根据嵌套的上下文来生成。
        String id = resultMapNode.getStringAttribute("id",
                resultMapNode.getValueBasedIdentifier());

        // 获取返回类型 依次读取:【type】>【ofType】>【resultType】>【javaType】
        String type = resultMapNode.getStringAttribute("type",
                resultMapNode.getStringAttribute("ofType",
                        resultMapNode.getStringAttribute("resultType",
                                resultMapNode.getStringAttribute("javaType"))));

        // 获取当前ResultMap是否继承了其他ResultMap
        String extend = resultMapNode.getStringAttribute("extends");

        // 获取自动映射标志
        Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");

        // 解析出返回类型
        Class<?> typeClass = resolveClass(type);
        if (typeClass == null) {
            // 嵌套映射时,外部对象属性类型定义优先级较低
            typeClass = inheritEnclosingType(resultMapNode, enclosingType);
        }

        Discriminator discriminator = null;

        // 返回结果定义
        List<ResultMapping> resultMappings = new ArrayList<>();
        // 添加所有额外的ResultMap集合
        resultMappings.addAll(additionalResultMappings);

        List<XNode> resultChildren = resultMapNode.getChildren();
        // 开始处理ResultMap中的每一个子节点
        for (XNode resultChild : resultChildren) {
            // 获取每一个ResultMap的子节点 处理constructor节点，该节点用来配置构造方法
            if ("constructor".equals(resultChild.getName())) {
                // 处理constructor节点
                processConstructorElement(resultChild, typeClass, resultMappings);

            } else if ("discriminator".equals(resultChild.getName())) {
                // 处理discriminator节点（鉴别器）
                // 通过配置discriminator节点可以实现根据查询结果动态生成查询语句的功能
                discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);

            } else {
                // 获取ID标签
                List<ResultFlag> flags = new ArrayList<>();
                if ("id".equals(resultChild.getName())) {
                    // 添加ID标记
                    flags.add(ResultFlag.ID);
                }

                // 添加ResultMapping配置
                resultMappings.add(
                        buildResultMappingFromContext(
                                resultChild
                                , typeClass
                                , flags
                        )
                );
            }
        }

        // 构建ResultMap解析器
        ResultMapResolver resultMapResolver = new ResultMapResolver(
                builderAssistant
                , id /*resultMap的ID*/
                , typeClass /*返回类型*/
                , extend /*继承的ResultMap*/
                , discriminator /*鉴别器*/
                , resultMappings /*内部的ResultMapping集合*/
                , autoMapping /*自动映射*/
        );

        try {
            // 解析ResultMap
            return resultMapResolver.resolve();
        } catch (IncompleteElementException e) {
            // 解析ResultMap发生异常，将奖盖ResultMap放入未完成解析的ResultMap集合.
            configuration.addIncompleteResultMap(resultMapResolver);
            throw e;
        }
    }

    protected Class<?> inheritEnclosingType(XNode resultMapNode, Class<?> enclosingType) {
        // 一对一集合映射
        if ("association".equals(resultMapNode.getName()) && resultMapNode.getStringAttribute("resultMap") == null) {
            // 通过属性定义推断java类型
            String property = resultMapNode.getStringAttribute("property");
            if (property != null && enclosingType != null) {
                MetaClass metaResultType = MetaClass.forClass(enclosingType, configuration.getReflectorFactory());
                return metaResultType.getSetterType(property);
            }
        } else if ("case".equals(resultMapNode.getName()) && resultMapNode.getStringAttribute("resultMap") == null) {
            // 鉴别器
            return enclosingType;
        }
        return null;
    }

    /**
     * 处理resultMap->constructor节点的内容
     *
     * @param resultChild    constructor节点
     * @param resultType     返回类型
     * @param resultMappings resultMap集合
     */
    private void processConstructorElement(XNode resultChild, Class<?> resultType, List<ResultMapping> resultMappings) throws Exception {
        // 获取constructor所有的子节点
        List<XNode> argChildren = resultChild.getChildren();

        for (XNode argChild : argChildren) {
            List<ResultFlag> flags = new ArrayList<>();
            // 表示属于构造参数
            flags.add(ResultFlag.CONSTRUCTOR);
            if ("idArg".equals(argChild.getName())) {
                // 主键标记
                flags.add(ResultFlag.ID);
            }
            resultMappings.add(
                    buildResultMappingFromContext(
                            argChild /*constructor元素的idArg或者arg子元素*/
                            , resultType/*构造方法对应的java对象*/
                            , flags /*参数类型标记*/
                    )
            );
        }
    }

    /**
     * 解析鉴别器
     *
     * @param context        鉴别器上下文
     * @param resultType     返回类型
     * @param resultMappings 已有的ResultMap集合
     */
    private Discriminator processDiscriminatorElement(XNode context, Class<?> resultType, List<ResultMapping> resultMappings) throws Exception {
        // 获取字段名称
        String column = context.getStringAttribute("column");
        // 获取java类型
        String javaType = context.getStringAttribute("javaType");
        // 获取jdbc类型
        String jdbcType = context.getStringAttribute("jdbcType");
        // 获取类型处理器
        String typeHandler = context.getStringAttribute("typeHandler");
        // 获取真实的java类型
        Class<?> javaTypeClass = resolveClass(javaType);
        // 获取真实的类型处理器
        Class<? extends TypeHandler<?>> typeHandlerClass = resolveClass(typeHandler);
        // 获取真实的jdbc类型
        JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);

        // 处理鉴别器
        Map<String, String> discriminatorMap = new HashMap<>();
        for (XNode caseChild : context.getChildren()) {
            // 解析case代码块
            // 解析case代码块的value标记
            String value = caseChild.getStringAttribute("value");
            // 解析case代码块的ResultMap标记
            String resultMap = caseChild.getStringAttribute("resultMap" /*使用指定的resultMap*/
                    , processNestedResultMappings(caseChild, resultMappings, resultType/*如果没有指定resultMap，则动态生成ResultMap实例*/
                    )
            );
            // 鉴别器存放值和resultMap的对应关系
            discriminatorMap.put(value, resultMap);
        }
        // 构造鉴别器
        return builderAssistant.buildDiscriminator(
                resultType /*返回类型*/
                , column /*对应的字段*/
                , javaTypeClass /*字段类型*/
                , jdbcTypeEnum /*jdbc类型*/
                , typeHandlerClass/*类型转换处理器*/
                , discriminatorMap /*鉴别器映射集合*/
        );
    }

    /**
     * 解析并注册 所有的Sql元素
     * 会解析所有没有指定数据库标志的SQL片段以及当前数据库类型的SQL片段
     * 此处只是简单的将所有的SQL片段读取出来，然后放到{@link Configuration#sqlFragments}中。
     *
     * @param list 所有的/mapper/sql节点
     */
    private void sqlElement(List<XNode> list) {
        if (configuration.getDatabaseId() != null) {
            // 获取当前数据库类型的专用SQL片段
            sqlElement(list, configuration.getDatabaseId());
        }
        // 获取所有没有指定数据库类型的SQL片段
        sqlElement(list, null);
    }

    /**
     * 解析并注册Sql节点代码块
     *
     * @param list               所有的SQL节点
     * @param requiredDatabaseId 当前的数据库类型标志
     */
    private void sqlElement(List<XNode> list, String requiredDatabaseId) {
        for (XNode context : list) {
            // 获取数据库类型标志
            String databaseId = context.getStringAttribute("databaseId");
            // 获取Sql代码块的唯一标志
            String id = context.getStringAttribute("id");
            // 将唯一标志和当前命名空间结合
            id = builderAssistant.applyCurrentNamespace(id, false);
            if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) {
                // 当前Sql代码块属于当前数据库类型,保留当前代码块
                sqlFragments.put(id, context);
            }
        }
    }

    private boolean databaseIdMatchesCurrent(String id, String databaseId, String requiredDatabaseId) {
        if (requiredDatabaseId != null) {
            if (!requiredDatabaseId.equals(databaseId)) {
                return false;
            }
        } else {
            if (databaseId != null) {
                return false;
            }
            // skip this fragment if there is a previous one with a not null databaseId
            if (this.sqlFragments.containsKey(id)) {
                XNode context = this.sqlFragments.get(id);
                if (context.getStringAttribute("databaseId") != null) {
                    return false;
                }
            }
        }
        return true;
    }

    /**
     * 构建resultMapping对象
     *
     * @param context    ResultMapping代码块
     * @param resultType 返回类型
     * @param flags      参数类型标记（构造?主键？）
     */
    private ResultMapping buildResultMappingFromContext(XNode context, Class<?> resultType, List<ResultFlag> flags) throws Exception {
        String property;
        if (flags.contains(ResultFlag.CONSTRUCTOR)) {
            // 如果当前节点定义的是一个构造参数，那么读取的是其name属性（形参名称）。
            property = context.getStringAttribute("name");
        } else {
            // 不是构造参数，读取property属性
            property = context.getStringAttribute("property");
        }

        // 对应的JDBC列名称
        String column = context.getStringAttribute("column");
        // 对应的java类型
        String javaType = context.getStringAttribute("javaType");
        // 对应的jdbc类型
        String jdbcType = context.getStringAttribute("jdbcType");

        // 是否引用了其他select语句
        String nestedSelect = context.getStringAttribute("select");
        /*
            resultMap中可以包含association或者collection这种复合节点，这些复合类型可以使用外部定义的resultMap或者内嵌的resultMap,
            因此针对这里的处理逻辑是：如果有resultMap就获取，没有则动态生成一个，动态生成的resultMap的唯一标志是基于XNode#getValueBasedIdentifier计算得来的。
         */
        String nestedResultMap = context.getStringAttribute(
                "resultMap",/*使用指定的resultMap*/
                processNestedResultMappings(context, Collections.<ResultMapping>emptyList(), resultType)/*这里表示默认值，如果没有则动态生成一个ResultMap*/
        );

        // 默认情况下，子对象仅在至少一个列映射到其属性非空时才创建。
        // 通过对这个属性指定非空的列将改变默认行为，这样做之后Mybatis将仅在这些列非空时才创建一个子对象。
        // 可以指定多个列名，使用逗号分隔。默认值：未设置(unset)。
        String notNullColumn = context.getStringAttribute("notNullColumn");

        // 当连接多表时，你将不得不使用列别名来避免ResultSet中的重复列名。
        // 因此你可以指定columnPrefix映射列名到一个外部的结果集中。
        String columnPrefix = context.getStringAttribute("columnPrefix");

        // 类型转换处理器
        String typeHandler = context.getStringAttribute("typeHandler");

        // 获取resultSet集合
        String resultSet = context.getStringAttribute("resultSet");

        // 标识出包含foreign keys的列的名称
        String foreignColumn = context.getStringAttribute("foreignColumn");

        // 懒加载
        boolean lazy = "lazy".equals(context.getStringAttribute("fetchType", configuration.isLazyLoadingEnabled() ? "lazy" : "eager"));

        // 解析java类型
        Class<?> javaTypeClass = resolveClass(javaType);
        // 解析类型处理器
        Class<? extends TypeHandler<?>> typeHandlerClass = resolveClass(typeHandler);
        // 解析出jdbc类型
        JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);

        // 创建最终的resultMap
        return builderAssistant.buildResultMapping(resultType, property, column, javaTypeClass, jdbcTypeEnum, nestedSelect, nestedResultMap, notNullColumn, columnPrefix, typeHandlerClass, flags, resultSet, foreignColumn, lazy);
    }

    /**
     * 处理嵌套的ResultMap，作用于处理association或者collection节点、
     * <p>
     * resultMap中可以包含association或者collection这种复合节点，这些复合类型可以使用外部定义的resultMap或者内嵌的resultMap,
     * 因此针对这里的处理逻辑是：如果有resultMap就获取，
     * 没有则动态生成一个，动态生成的resultMap的唯一标志是基于XNode#getValueBasedIdentifier计算得来的。
     *
     * @param context        父级XML代码块
     * @param resultMappings 已有的resultMapping集合
     * @param enclosingType  返回类型
     */
    private String processNestedResultMappings(XNode context, List<ResultMapping> resultMappings, Class<?> enclosingType) throws Exception {
        if ("association".equals(context.getName())
                || "collection".equals(context.getName())
                || "case".equals(context.getName())) {
            // association和collection property有select属性，这里只处理非select参数的代码块
            /*
             * select可以指定另外一个映射语句的ID，加载这个属性映射需要的复杂类型。
             * 在列属性中指定的列值将会被传递给目标select语句中作为参数。
             */
            if (context.getStringAttribute("select") == null) {
                // 没有指定select属性
                validateCollection(context, enclosingType);
                // 解析嵌套的resultMap元素
                ResultMap resultMap =
                        resultMapElement(context, resultMappings, enclosingType);
                return resultMap.getId();
            }
        }
        return null;
    }

    /**
     * 验证集合
     *
     * @param context       XML代码块
     * @param enclosingType 返回类型
     */
    protected void validateCollection(XNode context, Class<?> enclosingType) {
        if ("collection".equals(context.getName()) /*处理Collection集合*/
                && context.getStringAttribute("resultMap") == null/*没有定义resultMap*/
                && context.getStringAttribute("resultType") == null/*没有定义resultType*/
        ) {
            // 解析collection内部块

            // 获取将要返回类型的类型元数据
            MetaClass metaResultType = MetaClass.forClass(enclosingType, configuration.getReflectorFactory());

            /*获取其property值，该值对应返回类的字段*/
            String property = context.getStringAttribute("property");
            if (!metaResultType.hasSetter(property)) {
                throw new BuilderException(
                        "Ambiguous collection type for property '" + property + "'. You must specify 'resultType' or 'resultMap'.");
            }
        }
    }

    /**
     * 绑定Mapper和命名空间的关系,这里的mapper代指DAO操作接口
     */
    private void bindMapperForNamespace() {
        String namespace = builderAssistant.getCurrentNamespace();
        if (namespace != null) {
            Class<?> boundType = null;
            try {
                // 加载Mapper文件对应的Dao操作接口
                boundType = Resources.classForName(namespace);
            } catch (ClassNotFoundException e) {
                //ignore, bound type is not required
            }
            if (boundType != null) {
                if (!configuration.hasMapper(boundType)) {
                    // 如果尚未绑定该资源则执行下列处理
                    // Spring may not know the real resource name so we set a flag
                    // to prevent loading again this resource from the mapper interface
                    // look at MapperAnnotationBuilder#loadXmlResourceloadXmlResource
                    // 注册已加载过的资源集合
                    configuration.addLoadedResource("namespace:" + namespace);
                    // 注册DAO操作接口
                    configuration.addMapper(boundType);
                }
            }
        }
    }

}
